package org.apache.ibatis.config.binding;

import org.apache.ibatis.common.annotations.Flush;
import org.apache.ibatis.common.annotations.MapKey;
import org.apache.ibatis.common.reflection.MetaObject;
import org.apache.ibatis.common.reflection.ParamNameResolver;
import org.apache.ibatis.common.reflection.TypeParameterResolver;
import org.apache.ibatis.config.mapping.SqlCommandType;
import org.apache.ibatis.sql.session.Configuration;
import org.apache.ibatis.sql.session.RowBounds;
import org.apache.ibatis.sql.session.SqlSession;
import sun.plugin2.main.server.ResultHandler;

import java.lang.reflect.Array;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.*;

/**
 * @Name MapperMethod
 * @Description: Mapper 映射方法 [数据库操作节点] 类
 * 描述信息：
 * 前提：要想将一个数据库操作接入一个抽象方法中，首先要中的就是将数据库操作节点转化为一个具体的方法。
 * 作用：每个 MapperMethod 对象对应了一个数据库操作节点。因此，通过 MapperMethod 类将一个数据库操作语句和一个 JAVA 抽象方法绑定在了一起。它的 MethodSignature 属性保存了这个抽象方法的详细信息；它的 SqlCommand 属性持有这个方法对应的 SQL 语句；它的 execute 方法可以触发节点中的 SQL 语句。只要调用 MapperMethod 对象的 execute 方法，就可以触发具体的数据库操作，于是数据库操作就被转化为了一个具体的方法。
 * 结论：在 MapperMethod 类的帮助下，只要我们（通过动态代理）能够将 JAVA 映射接口抽象方法的调用转为对 MapperMethod 对象 execute 方法的调用，就能在调用某个 JAVA 映射接口抽象方法时完成指定的数据库操作。
 * @Version v1.0.0
 * @Author: lichao
 * @Date: 2024-05-16 16:48:32
 **/
public class MapperMethod {
    private final MethodSignature method;
    private final SqlCommand sqlCommand;

    public MapperMethod(Class<?> mapperInterface, Method method, Configuration config) {
        this.method = new MethodSignature(config, mapperInterface, method);
        this.sqlCommand = new SqlCommand(config, mapperInterface, method);
    }

    /**
     * （核心）执行映射接口中的抽象方法
     *
     * @param sqlSession 数据库连接对象，通过它可以执行 SQL 语句
     * @param args       抽象方法中的参数
     * @return java.lang.Object 数据库操作结果
     * @version v1.0.0
     * @author lichao
     * @date 2024-05-16 17:47:43
     */
    public Object execute(SqlSession sqlSession, Object[] args) {
        Object result;
        // 根据 SQL 语句的类型（增、删、改、查、清除缓存、未知），执行不同的操作
        switch (sqlCommand.getType()) {
            case INSERT: {
                // 将参数顺序与实参对应
                Object param = method.convertArgsToSqlCommandParam(args);
                // 执行操作并返回结果
                result = rowCountResult(sqlSession.insert(sqlCommand.getName(), param));
                break;
            }
            case UPDATE: {
                Object param = method.convertArgsToSqlCommandParam(args);
                result = rowCountResult(sqlSession.update(sqlCommand.getName(), param));
                break;
            }
            case DELETE: {
                Object param = method.convertArgsToSqlCommandParam(args);
                result = rowCountResult(sqlSession.delete(sqlCommand.getName(), param));
                break;
            }
            case SELECT: {
                // void 结果，且有结果处理器
                if (method.returnsVoid && method.hasResultHandler()) {
                    // 使用结果处理器执行查询
                    executeWithResultHandler(sqlSession, args);
                    result = null;
                }
                // 多条结果
                else if (method.returnsMany) result = executeForMany(sqlSession, args);
                    // Map 结果
                else if (method.returnsMap) result = executeForMap(sqlSession, args);
                    // 游标类型结果
                else if (method.returnsCursor) result = executeForCursor(sqlSession, args);
                    // 单条结果
                else {
                    Object param = method.convertArgsToSqlCommandParam(args);
                    result = sqlSession.selectOne(sqlCommand.getName(), param);
                    if (method.returnsOptional && (result == null || !method.getReturnType().equals(result.getClass()))) {
                        result = Optional.ofNullable(result);
                    }
                }
                break;
            }
            case FLUSH: {
                // 清除缓存语句
                result = sqlSession.flushStatements();
                break;
            }
            default:
                throw new BindingException("未知的执行方法：" + sqlCommand.getName());
        }
        // 如果查询结果为 null，但返回类型为基本数据类型，此时返回变量无法接收查询结果，抛出异常。
        if (result == null && method.getReturnType().isPrimitive() && !method.returnsVoid) {
            String msg = "映射方法 [" + sqlCommand.getName() + "] 试图从返回类型为基本数据类型 '" + method.getReturnType() + "' 的执行方法中返回 null 值。";
            throw new BindingException(msg);
        }
        return result;
    }

    private Object rowCountResult(int rowCount) {
        final Object result;
        if (method.returnsVoid) result = null;
        else if (Integer.class.equals(method.getReturnType()) || Integer.TYPE.equals(method.getReturnType()))
            result = rowCount;
        else if (Long.class.equals(method.getReturnType()) || Long.TYPE.equals(method.getReturnType()))
            result = (long) rowCount;
        else if (Boolean.class.equals(method.getReturnType()) || Boolean.TYPE.equals(method.getReturnType()))
            result = rowCount > 0;
        else {
            String msg = "映射方法 [" + sqlCommand.getName() + "] 存在一个不支持的返回值类型：" + method.getReturnType();
            throw new BindingException(msg);
        }
        return result;
    }

    private void executeWithResultHandler(SqlSession sqlSession, Object[] args) {
        MappedStatement ms = sqlSession.getConfiguration().getMappedStatement(sqlCommand.getName());
        if (!StatementYype.CALLABLE.equals(ms.getStatementType()) && void.class.equals(ms.getResultMaps().get(0).getType())) {
            String msg = "映射方法 [" + sqlCommand.getName() + "] 需要一个 @ResultMap 注解、@ResultType 注解或 XML 中的 ResultType 属性，以便于将 ResultHandler 结果处理器传入为一个参数。";
            throw new BindingException(msg);
        }
        Object param = method.convertArgsToSqlCommandParam(args);
        if (method.hasRowBounds()) {
            RowBounds rowBounds = method.extractRowBounds(args);
            sqlSession.select(sqlCommand.getName(), param, rowBounds, method.extractResultHandler(args));
        } else {
            sqlSession.select(sqlCommand.getName(), param, method.extractResultHandler(args));
        }
    }

    private <E> Object executeForMany(SqlSession sqlSession, Object[] args) {
        List<E> result;
        Object param = method.convertArgsToSqlCommandParam(args);
        if (method.hasRowBounds()) {
            RowBounds rowBounds = method.extractRowBounds(args);
            result = sqlSession.selectList(command.getName(), param, rowBounds);
        } else {
            result = sqlSession.selectList(command.getName(), param);
        }
        // 支持集合和数组类型
        if (!method.getReturnType().isAssignableFrom(result.getClass())) {
            if (method.getReturnType().isArray()) {
                return convertToArray(result);
            } else {
                return convertToDeclaredCollection(sqlSession.getConfiguration(), result);
            }
        }
        return result;
    }

    private <E> Object convertToArray(List<E> list) {
        Class<?> arrayComponentType = method.getReturnType().getComponentType();
        Object array = Array.newInstance(arrayComponentType, list.size());
        if (arrayComponentType.isPrimitive()) {
            for (int i = 0; i < list.size(); i++) {
                Array.set(array, i, list.get(i));
            }
            return array;
        } else return list.toArray((E[]) array);
    }

    private <E> Object convertToDeclaredCollection(Configuration config, List<E> list) {
        Object collection = config.getObjectFactory().create(method.getReturnType());
        MetaObject metaObject = config.newMetaObject(collection);
        metaObject.addAll(list);
        return collection;
    }

    private <K, V> Map<K, V> executeForMap(SqlSession sqlSession, Object[] args) {
        Map<K, V> result;
        Object param = method.convertArgsToSqlCommandParam(args);
        if (method.hasRowBounds()) {
            RowBounds rowBounds = method.extractRowBounds(args);
            result = sqlSession.selectMap(sqlCommand.getName(), param, method.getMapKey(), rowBounds);
        } else {
            result = sqlSession.selectMap(sqlCommand.getName(), param, method.getMapKey());
        }
        return result;
    }

    private <T> Cursor<T> executeForCursor(SqlSession sqlSession, Object[] args) {
        Cursor<T> result;
        Object param = method.convertArgsToSqlCommandParam(args);
        if (method.hasRowBounds()) {
            RowBounds rowBounds = method.extractRowBounds(args);
            result = sqlSession.selectList(sqlCommand.getName(), param, rowBounds);
        } else {
            result = sqlSession.selectList(sqlCommand.getName(), param);
        }
        return result;
    }

    public static class ParamMap<V> extends HashMap<String, V> {
        private static final long serialVersionUID = 6870321188537721055L;

        @Override
        public V get(Object key) {
            if (!super.containsKey(key)) {
                String errorMsg = "没有找到参数 [" + key + "]，可用的参数包括：" + keySet();
                throw new BindingException(errorMsg);
            }
            return super.get(key);
        }
    }

    public static class MethodSignature {
        // 参数名称解析器
        private final ParamNameResolver paramNameResolver;
        // rowBounds 参数的位置
        private final Integer rowBoundsIndex;
        // resultHandler 的位置
        private final Integer resultHandlerIndex;
        // 返回类型
        private final Class<?> returnType;
        // 如果返回类型为 Map 类型，记录所有的 Key 键
        private final String mapKey;
        // 返回类型是否为集合类型
        private final boolean returnsMany;
        // 返回类型是否为 Map 类型
        private final boolean returnsMap;
        // 返回类型是否为 void
        private final boolean returnsVoid;
        // 返回类型是否为 Cursor 类型
        private final boolean returnsCursor;
        // 返回类型是否为 Optional 类型
        private final boolean returnsOptional;

        public MethodSignature(Configuration config, Class<?> mapperInterface, Method method) {
            this.paramNameResolver = new ParamNameResolver(config, method);
            this.rowBoundsIndex = getUniqueParamIndex(method, RowBounds.class);
            this.resultHandlerIndex = getUniqueParamIndex(method, ResultHandler.class);
            Type resolvedReturnType = TypeParameterResolver.resolveReturnType(method, mapperInterface);
            if (resolvedReturnType instanceof Class<?>) this.returnType = (Class<?>) resolvedReturnType;
            else if (resolvedReturnType instanceof ParameterizedType)
                this.returnType = (Class<?>) ((ParameterizedType) resolvedReturnType).getRawType();
            else this.returnType = method.getReturnType();
            this.mapKey = getMapKey(method);
            this.returnsMany = config.getObjectFactory().isCollection(this.returnType) || this.returnType.isArray();
            this.returnsMap = this.mapKey != null;
            this.returnsVoid = void.class.equals(this.returnType);
            this.returnsCursor = Cursor.class.equals(this.returnType);
            this.returnsOptional = Optional.class.equals(this.returnType);
        }

        public Object convertArgsToSqlCommandParam(Object[] args) {
            return paramNameResolver.getNamedParams(args);
        }

        public boolean hasRowBounds() {
            return rowBoundsIndex != null;
        }

        public RowBounds extractRowBounds(Object[] args) {
            return hasRowBounds() ? (RowBounds) args[rowBoundsIndex] : null;
        }

        public boolean hasResultHandler() {
            return resultHandlerIndex != null;
        }

        public ResultHandler extractResultHandler(Object[] args) {
            return hasResultHandler() ? (ResultHandler) args[resultHandlerIndex] : null;
        }

        public String getMapKey() {
            return mapKey;
        }

        public Class<?> getReturnType() {
            return returnType;
        }

        public boolean isReturnsMany() {
            return returnsMany;
        }

        public boolean isReturnsMap() {
            return returnsMap;
        }

        public boolean isReturnsVoid() {
            return returnsVoid;
        }

        public boolean isReturnsCursor() {
            return returnsCursor;
        }

        public boolean isReturnsOptional() {
            return returnsOptional;
        }

        /**
         * 获取指定参数的索引
         */
        private Integer getUniqueParamIndex(Method method, Class<?> paramType) {
            Integer index = null;
            final Class<?>[] argTypes = method.getParameterTypes();
            for (int i = 0; i < argTypes.length; i++) {
                if (paramType.isAssignableFrom(argTypes[i])) {
                    if (index == null) index = i;
                    else {
                        String msg = "方法 [" + method.getName() + "] 不允许存在多个类型为 {" + paramType.getSimpleName() + "} 的参数。";
                        throw new BindingException(msg);
                    }
                }
            }
            return index;
        }

        /**
         * 如果返回类型为 Map 类型，获取所有的 Key 键
         */
        private String getMapKey(Method method) {
            String mapKey = null;
            if (Map.class.isAssignableFrom(method.getReturnType())) {
                final MapKey mapKeyAnnotation = method.getAnnotation(MapKey.class);
                if (mapKeyAnnotation != null) mapKey = mapKeyAnnotation.value();
            }
            return mapKey;
        }

    }

    public static class SqlCommand {
        // SQL 语句的名称
        private final String name;
        // SQL 语句的种类（增、删、改、查、清除缓存、未知）
        private final SqlCommandType type;

        public SqlCommand(Configuration config, Class<?> mapperInterface, Method method) {
            final String methodName = method.getName();
            // 方法所在的类，可能是 mapperInterface，也可能是它的子类
            final Class<?> declaringClass = method.getDeclaringClass();
            MappedStatement ms = resolveMappedStatement(mapperInterface, methodName, declaringClass, config);
            if (ms == null) {
                if (method.getAnnotation(Flush.class) != null) {
                    this.name = null;
                    this.type = SqlCommandType.FLUSH;
                } else {
                    String msg = "无效的 SQL 绑定语句（没有发现）：" + mapperInterface.getName() + "." + methodName;
                    throw new BindingException(msg);
                }
            } else {
                this.name = ms.getId();
                this.type = ms.getSqlCommandType();
                if (this.type == SqlCommandType.UNKNOWN) {
                    String msg = "未知的 SQL 语句：" + name;
                    throw new BindingException(msg);
                }
            }
        }

        public String getName() {
            return name;
        }

        public SqlCommandType getType() {
            return type;
        }

        /**
         * 解析映射接口抽象方法对应的 MappedStatement 对象（数据库操作语句）
         *
         * @param mapperInterface 映射接口
         * @param methodName      抽象方法
         * @param declaringClass  抽象方法所在的类，一般是映射接口本身，也可能是它的子类
         * @param config          配置信息
         * @return MappedStatement 对应的数据库操作语句
         * @version v1.0.0
         * @author lichao
         * @date 2024-05-16 17:37:56
         */
        private MappedStatement resolveMappedStatement(Class<?> mapperInterface, String methodName, Class<?> declaringClass, Configuration config) {
            // 数据库操作语句 ID [映射接口.抽象方法]
            String statementId = mapperInterface.getName() + "." + methodName;
            // Configuration 对象中保存了解析后的所有数据库操作语句
            if (config.hasStatement(statementId)) return config.getMappedStatement(statementId);
            else if (mapperInterface.equals(declaringClass)) return null;
            // 从抽象方法的定义类开始，递归向上（父类）寻找
            for (Class<?> superInterface : mapperInterface.getInterfaces()) {
                if (declaringClass.isAssignableFrom(superInterface)) {
                    MappedStatement ms = resolveMappedStatement(superInterface, methodName, declaringClass, config);
                    if (ms != null) return ms;
                }
            }
            return null;
        }

    }

}

